package edu.whu.rose.clock.ssql.graph;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashSet;

import com.sleepycat.bind.tuple.IntegerBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;

import edu.whu.rose.clock.ssql.binding.IntArrayBinding;
import edu.whu.rose.clock.ssql.binding.SubGraphBinding;
import edu.whu.rose.clock.ssql.graphindex.GraphIndexManager;

public class SubGraphDB {

	private String dbEnv;
	private Environment env;
	
	private Database subgraphs;
	private Database sgkn;
	private Database invList;
	
	public SubGraphDB() {
		this.dbEnv = "dbEnv_subgraph";
	}
	
	public SubGraphDB(String dbEnv) {
		this.dbEnv = dbEnv;
	}
	
	public boolean initEnv() {
		EnvironmentConfig envConfig = new EnvironmentConfig();
		envConfig.setAllowCreate(true);
		try {
			this.env = new Environment(new File(dbEnv), envConfig);
			return true;
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: opening database environment");
			return false;
		}
	}
	
//	public Database openDB(String dbName) {
//		try {
//			DatabaseConfig dbConfig = new DatabaseConfig();
//			dbConfig.setAllowCreate(true);
//			return env.openDatabase(null, dbName, dbConfig);
//		}
//		catch (DatabaseException ex) {
//			ex.printStackTrace();
//			System.err.println("Error: open database \"" + dbName + "\"");
//			return null;
//		}
//	}
	
	public boolean openDB() {
		try {
			DatabaseConfig dbConfig = new DatabaseConfig();
			dbConfig.setAllowCreate(true);
			subgraphs = env.openDatabase(null, "subgraphs", dbConfig);
			sgkn = env.openDatabase(null, "sgkn", dbConfig);
			invList = env.openDatabase(null, "invList", dbConfig);
			return true;
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: opening databases");
			return false;
		}
	}
	
	public void closeDB() {
		try {
			if (invList != null) {
				invList.close();
			}
			if (sgkn != null) {
				sgkn.close();
			}
			if (subgraphs != null) {
				subgraphs.close();
			}
			if (env != null) {
				env.close();
			}
		}
		catch (DatabaseException ex) {
			System.err.println("Error: closing databases and environment.");
			ex.printStackTrace();
		}
	}
	
	public Cursor getCursorOfSubgraphs() throws DatabaseException {
		return subgraphs.openCursor(null, null);
	}
	
	public void reorder() {
		Cursor cursor = null;
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		int id = 0;
		IntegerBinding ib = new IntegerBinding();
		
		try {
			cursor = this.subgraphs.openCursor(null, null);
			while (cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				id++;
				ib.objectToEntry(new Integer(id), key);
				cursor.delete();
				cursor.put(key, value);
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
		}
		finally {
			try {
				if (cursor != null) {
					cursor.close();
				}
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
				System.err.println("Error: closing cursor");
			}
		}
	}
	
	public void computeSGKN(BufferedReader br, HashSet<String> stopwordlist) {
		try {
			long size = this.subgraphs.count();
			int[] sgknArr = new int[(int)size];
			
			while (br.ready()) {
				String line = br.readLine().trim();
				if (line.length() > 0) {
					int pos = line.indexOf(" ");
					if (pos < 1) {
						System.err.println("Found wrong line: " + line);
						continue;
					}
					String key = line.substring(0, pos);
					String value = line.substring(pos).trim();
					
					//get corresponding subgraphs from vertex id (key)
					int vertex = new Integer(key).intValue();
					int[] subgraphs = null;
					subgraphs = this.getFromInvertedList(vertex);
					
					String[] pairs = value.split("@@");
					for (int i = 0; i < pairs.length; i++) {
						String pair = pairs[i].trim();
						if (pair.length() > 0) {
							pos = pair.indexOf(":");
							if (pos < 1) {
								System.out.println("Found wrong attr-value pair: " + pair);
								continue;
							}
							String attrName = pair.substring(0, pos).trim();
							String attrValue = pair.substring(pos + 1).trim();
							String[] keywords = null;
							if (attrName.equals("title")) {
								keywords = GraphIndexManager.parse(attrValue);
							}
							else {
								keywords = GraphIndexManager.naiveParse(attrValue);
							}
							for (int j = 0; j < keywords.length; j++) {
								String keyword = keywords[j].trim();
								if (keyword.length() > 0) {
									if (stopwordlist.contains(keyword)) {
										continue;
									}

									for (int k = 0; k < subgraphs.length; k++) {
										sgknArr[subgraphs[k] - 1]++;
									}
								}
							}
						}
					}
				}
			}
			this.addAllSGKN(sgknArr);
		}
		catch (IOException ex) {
			ex.printStackTrace();
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
		}
	}
	
	public void insertSubGraph(int row, RRadiusSubGraph rrsg) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		IntegerBinding.intToEntry(row, key);
		SubGraphBinding sgb = new SubGraphBinding();
		sgb.objectToEntry(rrsg, value);
		
		subgraphs.put(null, key, value);
	}
	
	public RRadiusSubGraph getSubGraph(int row) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		IntegerBinding.intToEntry(row, key);
		if (subgraphs.get(null, key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			SubGraphBinding sgb = new SubGraphBinding();
			RRadiusSubGraph rrsg = (RRadiusSubGraph)sgb.entryToObject(value);
			rrsg.setRow(row);
			return rrsg;
		}
		else {
			return null;
		}
	}
	
	public void removeSubGraph(int row) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		IntegerBinding.intToEntry(row, key);
		subgraphs.delete(null, key);
	}
	
	public void genSubGraphInvertedList(int rank) {
		VertexSubgraphMappingCache ilc = new VertexSubgraphMappingCache(rank, 5000, this);
		
		Cursor cursor = null;
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		
		try {
			cursor = this.subgraphs.openCursor(null, null);
			while (cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				int row = IntegerBinding.entryToInt(key);
				SubGraphBinding sgb = new SubGraphBinding();
				RRadiusSubGraph rrsg = (RRadiusSubGraph)sgb.entryToObject(value);
				int[] vertexes = rrsg.getVertexes();
				for (int i = 0; i < vertexes.length; i++) {
					ilc.insert(vertexes[i] - 1, row);
				}
			}
			ilc.flush();
			ilc.statistic();
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		finally {
			try {
				if (cursor != null) {
					cursor.close();
				}
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
				System.err.println("Error: closing cursor");
			}
		}
	}
	
	public void insertToInvertedList(int vertex, int[] sgs) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		IntegerBinding.intToEntry(vertex, key);
		IntArrayBinding iab = new IntArrayBinding();
		iab.objectToEntry(sgs, value);
		
		invList.put(null, key, value);
	}
	
	public int[] getFromInvertedList(int vertex) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		IntegerBinding.intToEntry(vertex, key);
		if (invList.get(null, key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			IntArrayBinding iab = new IntArrayBinding();
			int[] sgs = (int[])iab.entryToObject(value);
			return sgs;
		}
		else {
			return null;
		}
	}
	
	public void printSubGraphs(FileWriter fw) {
		Cursor cursor = null;
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		SubGraphBinding sgb = new SubGraphBinding();
		
		try {
			cursor = this.subgraphs.openCursor(null, null);
			while (cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				int id = IntegerBinding.entryToInt(key);
				RRadiusSubGraph rrsg = (RRadiusSubGraph)sgb.entryToObject(value);
				fw.write(id + ":  ");
				int[] vertexes = rrsg.getVertexes();
				for (int i = 0; i < vertexes.length; i++) {
					fw.write(vertexes[i] + "  ");
				}
				int[] startVertexes = rrsg.getStartVertexes();
				if (startVertexes == null) {
					continue;
				}
				for (int i = 0; i < startVertexes.length; i++) {
					fw.write("(" + rrsg.getStartVertexes()[i] + ",");
					fw.write(rrsg.getEndVertexes()[i] + ")  ");
				}
				fw.write("\n");
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		catch (IOException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		finally {
			try {
				if (cursor != null) {
					cursor.close();
				}
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
				System.err.println("Error: closing cursor");
			}
		}
	}
	
	public void printInvList(FileWriter fw) {
		Cursor cursor = null;
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		
		try {
			cursor = this.invList.openCursor(null, null);
			while (cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				int vertex = IntegerBinding.entryToInt(key);
				IntArrayBinding iab = new IntArrayBinding();
				int[] sgs = (int[])iab.entryToObject(value);
				fw.write(vertex + ":  ");
				for (int i = 0; i < sgs.length; i++) {
					fw.write(sgs[i] + ", ");
				}
				fw.write("\n");
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		catch (IOException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		finally {
			try {
				if (cursor != null) {
					cursor.close();
				}
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
				System.err.println("Error: closing cursor");
			}
		}
	}
	
	public long getSGSize() throws DatabaseException {
		return subgraphs.count();
	}
	
	public void addAllSGKN(int[] sgknArr) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		for (int i = 0; i < sgknArr.length; i++) {
			IntegerBinding.intToEntry(i + 1, key);
			IntegerBinding.intToEntry(sgknArr[i], value);
			this.sgkn.put(null, key, value);
		}
	}
	
	public int getSGKN(int graph) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		IntegerBinding.intToEntry(graph, key);
		if (sgkn.get(null, key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			int result = IntegerBinding.entryToInt(value);
			return result;
		}
		return -1;
	}
	
//	public void insertRawSG(int row, RRadiusSubGraph subgraph) throws DatabaseException {
//		DatabaseEntry key = new DatabaseEntry();
//		DatabaseEntry value = new DatabaseEntry();
//		IntegerBinding.intToEntry(row, key);
//		SubGraphBinding sgb = new SubGraphBinding();
//		sgb.objectToEntry(subgraph, value);
//		
//		rawSG.put(null, key, value);
//	}
	
}
